#include "sur_impls.h"

sur_impls::sur_impls(const string& server, const string& user, const string& pw,
				const string& ukey_id, const string& cache_path, int cache_size) 
				: lcache_(cache_path, cache_size),
				server_(server), user_(user), pw_(pw), ukey_id_(ukey_id)
{
}
sur_impls::~sur_impls()
{
}
int sur_impls::write_hook(const void* buf, size_t n, void* data)
{
	FILE* f = static_cast<FILE*>(data);
	size_t ret = fwrite(buf, 1U, n, f);
	if (ferror(f) ) {
		LOG("fwrite() fail in %d.", ferror(f));
		return -1;
	}
	return static_cast<int>(ret);
}
int sur_impls::read_hook(void* buf, size_t n, void* data)
{
	FILE* f = static_cast<FILE*>(data);
	size_t ret = fread(buf, 1U, n, f);
	if (ferror(f) ) {
		LOG("fread() fail in %d.", ferror(f));
		return -1;
	}
	return static_cast<int>(ret);
}

int sur_impls::sur_init()
{
	LOG("Login in %s by %s[%s]", server_.c_str(), user_.c_str(), pw_.c_str() );
	//if Login fail, gw_ may be an invaild struct and never be used in any other function
	if(dicom::Login(server_, user_, pw_, ukey_id_, &gw_) != 0) {
		LOG("Login %s by %s[%s] fail, now exit.", server_.c_str(), user_.c_str(), "****");
		exit(1);
	}
	dicom::SetGwLogcallback(logger::ex_log_callback);
	return 0;
}
int sur_impls::sur_destroy()
{
	dicom::LogoutAndReleaseToken(gw_);
	gw_ = NULL;
	return 0;
}
int sur_impls::sur_open(const char *path, int flag, sur_fh *fh)
{
	if(fh == NULL) {
		return -EFAULT;
	}
	string tmp_name;
	int ret = 0;
	
	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -EINVAL ;
	}
	
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	} 
	
	dicom::DirEntry de;
	ret = dicom::Lookup(gw_, pid, name, &de);
	if (ret != 0) {
		LOG("OPEN lookup gw id for %s fail in %d.", path, ret);
		return -EIO;
	} 
	ret = lcache_.create_tmp_file(path, tmp_name);
	if(ret < 0) {
		return -ENOENT;
	} else if(ret == 0) {
		FILE* f = fopen(tmp_name.c_str(), "wb");
		ret = dicom::DownloadFile(gw_, de.id, sur_impls::write_hook, f);
		fclose(f);
		
		if(ret != 0) {
			LOG("Download file %s to %s fail.", path, tmp_name.c_str());
			lcache_.release_tmp_file(path);
			return -EIO;
		}
	}
	
	if(flag == O_RDONLY) {
		fh->tmp_fh_ = fopen(tmp_name.c_str(), "rb");
		fh->flag = 0;
		
	} else { // O_WRONLY || O_RDWR
		fh->tmp_fh_ = fopen(tmp_name.c_str(), "rb+");
		fh->flag = 1;
	}

	fh->tmp_name = tmp_name;
	fh->pos = 0;
	fh->file_name = name;
	fh->gw_id = de.id;
	fh->gw_pid = pid;
	pthread_mutex_init(&fh->mutex, NULL);

	return 0;
	
}
int sur_impls::sur_create(const char *path, sur_fh *fh)
{
	if(fh == NULL) {
		return -EFAULT;
	}
	string tmp_name;
	int ret = 0;

	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -EINVAL ;
	}
	
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	} 

	ret = lcache_.create_tmp_file(path, tmp_name);
	if(ret < 0) {
		return -ENOENT;
	}

	fh->tmp_fh_ = fopen(tmp_name.c_str(), "wb");
	fh->flag = 1;

	fh->tmp_name = tmp_name;
	fh->pos = 0;
	fh->file_name = name;
	fh->gw_id = "";
	fh->gw_pid = pid;
	
	DirEntry result;
	FILE* f = fopen(fh->tmp_name.c_str(), "rb");
	if(f == NULL) {
		LOG("CREATE open tmp file for %s fail.", path);
		fclose(fh->tmp_fh_);
		lcache_.release_tmp_file(path);
		return -EIO;
	}
	struct stat st;
	ret = stat(fh->tmp_name.c_str(), &st);
	if(ret != 0) {
		LOG("CREATE stat tmp file for %s fail.", path);
		fclose(fh->tmp_fh_);
		fclose(f);
		lcache_.release_tmp_file(path);
		return -EIO;
	}
	ret = dicom::UploadFile(gw_, fh->gw_pid, 
						  fh->file_name,
						  st.st_size, 
						  st.st_mtime, 
						  sur_impls::read_hook, 
						  f,
						  &result);
	fclose(f);
	
	if (ret != 0) {
		LOG("CREATE upload tmp file for %s fail in %d.", path, ret);
		fclose(fh->tmp_fh_);
		lcache_.release_tmp_file(path);
		return -EIO;
	} 

	return 0;
}
size_t sur_impls::sur_read(const char *path, char *buf, size_t size, off_t offset, sur_fh *fh)
{
	if (size == 0) {
        return 0;
    } else if (size < 0) {
		LOG("READ invalid read size in %ld.", size);
        return -1;
    }
	pthread_mutex_lock(&fh->mutex);
	int ret = 0;
	if(fh->pos != offset) {
		long x = offset - fh->pos;
		ret = fseek(fh->tmp_fh_, x, SEEK_CUR);
		if(ret < 0) {
			LOG("READ fseek to %ld error in %d.", x, ret);
			pthread_mutex_unlock(&fh->mutex);
			return -1;
		}
		fh->pos = offset;
	}

    size_t r = fread(buf, 1U, size, fh->tmp_fh_);
	if (ferror(fh->tmp_fh_) ) {
		LOG("READ fread error.");
		pthread_mutex_unlock(&fh->mutex);
		return -1;
	}
	fh->pos += r;
	pthread_mutex_unlock(&fh->mutex);
    return r;
}
size_t sur_impls::sur_write(const char *path, const char *buf, size_t size, off_t offset, sur_fh *fh)
{
	if (size == 0) {
        return 0;
    } else if (size < 0) {
		LOG("WRITE invalid write size in %ld.", size);
        return -1;
    }
	if(fh->flag != 2) {
		fh->flag = 2;
	}
	pthread_mutex_lock(&fh->mutex);
	int ret = 0;
	if(fh->pos != offset) {
		long x = offset - fh->pos;
		ret = fseek(fh->tmp_fh_, x, SEEK_CUR);
		if(ret < 0) {
			LOG("WRITE fseek to %ld error in %d.", x, ret);
			pthread_mutex_unlock(&fh->mutex);
			return -1;
		}
		fh->pos = offset;
	}

    size_t w = fwrite(buf, 1U, size, fh->tmp_fh_);
	if (ferror(fh->tmp_fh_) ) {
		LOG("WRITE fwrite error.");
		pthread_mutex_unlock(&fh->mutex);
		return -1;
	}
	fh->pos += w;
	pthread_mutex_unlock(&fh->mutex);
    return w;
}
int sur_impls::sur_release (const char *path, sur_fh *fh)
{
	if(fh == NULL) {
		return -EFAULT;
	}
	int ret = 0;
	fclose(fh->tmp_fh_);
	if(fh->flag == 2) { // if no change, ignore update
		
		DirEntry result;
		FILE* f = fopen(fh->tmp_name.c_str(), "rb");
		if(f == NULL) {
			LOG("RELEASE open tmp file %s error.", fh->tmp_name.c_str());
			return -EIO;
		}
		struct stat st;
		ret = stat(fh->tmp_name.c_str(), &st);
		if(ret != 0) {
			fclose(f);
			LOG("RELEASE stat tmp file %s error.", fh->tmp_name.c_str());
			return -EIO;
		}
		ret = dicom::UploadFile(gw_, fh->gw_pid, 
							  fh->file_name,
							  st.st_size, 
							  st.st_mtime, 
							  sur_impls::read_hook, 
							  f,
							  &result);
		fclose(f);
		
		if (ret != 0) {
			LOG("RELEASE upload tmp file fail in %d.", ret);
			return -EIO;
		} 
	} else {

	}
	
	lcache_.release_tmp_file(path);
	return 0;
	
}
int sur_impls::sur_unlink(const char *path)
{
	int ret = 0;
	
	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -EINVAL;
	}
	
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	} 
	
	dicom::DirEntry de;
	ret = dicom::Lookup(gw_, pid, name, &de);
	if (ret != 0) {
		return -ENOENT;
	} 
	
	ret = dicom::DeleteFile(gw_, de.id);
	if (ret != 0) {
		LOG("UNLINK delete file on gw fail in %d.", ret);
		return -EIO;
	} 
	lcache_.release_tmp_file(path);
	return 0;
}
int sur_impls::sur_flush(const char *path, sur_fh *fh)
{
	fflush(fh->tmp_fh_);
	return 0;
}

int sur_impls::sur_statfs(struct statvfs *st)
{
	if(st == NULL) {
		return -EINVAL;
	}
	int ret = 0;
	GwSpaceInfo info;
	ret = dicom::GetGwSpaceInfo(gw_, &info);
	
	if (ret != 0) {
		LOG("UNLINK delete file on gw fail in %d.", ret);
		return -EIO;
	} 
	
	const int64_t cap   = info.allSpace;
	const int64_t used  = info.usedSpace;
	const int64_t bsize = FS_BLKSIZE;
  
	st->f_bsize   =  bsize;
	st->f_frsize  =  bsize;
	st->f_blocks  =  cap/bsize;
	st->f_bfree   =  (cap - used)/bsize;
	st->f_bavail  =  (cap - used)/bsize;
	st->f_files   =  18153472;
	st->f_ffree   =  18153472;
	st->f_favail  =  18153472;
	st->f_fsid    =  1023;
	st->f_flag    =  ST_RDONLY | ST_NOSUID;
	st->f_namemax =  255;
	
	return 0;
}

int sur_impls::sur_truncate(const char *path, off_t size)
{
	//if(size == 0) {
	if(true) {
		string tmp_name;
		int ret = 0;
		
		string dir;
		string name;
		ret = deal_path(path, dir, name);
		if(ret != 0) {
			return -EINVAL;
		}
		string pid;
		ret = parse_pid(dir, pid);
		if (ret != 0) {
			return -EIO;
		}
	
		ret = lcache_.create_tmp_file(path, tmp_name);
		if(ret < 0) {
			return -ENOENT;
		} else if(ret > 0) {
			ret = truncate(tmp_name.c_str(), size);
			if(ret != 0) {
				LOG("TRUNCATE truncate tmp file %s fail in %d.", tmp_name.c_str(), ret);
				return -EIO;
			}
		}
		DirEntry result;
		FILE* f = fopen(tmp_name.c_str(), "rb");
		if(f == NULL) {
			LOG("TRUNCATE open tmp file %s fail.", tmp_name.c_str());
			return -EIO;
		}
		struct stat st;
		ret = stat(tmp_name.c_str(), &st);
		if(ret != 0) {
			LOG("TRUNCATE stat tmp file %s fail in %d.", tmp_name.c_str(), ret);
			fclose(f);
			return -EIO;
		}
		ret = dicom::UploadFile(gw_, pid, 
							  name, 
							  size, 
							  st.st_mtime, 
							  sur_impls::read_hook, 
							  f,
							  &result);
		fclose(f);
		if (ret != 0) {
			LOG("TRUNCATE upolad file on gw fail in %d.", ret);
			lcache_.release_tmp_file(path);
			return -EIO;
		} 

		lcache_.release_tmp_file(path);
	}
	return 0;
}
int sur_impls::sur_getattr(const char *path, struct stat *st)
{
	if(strcmp(path, "/") == 0) {
		st->st_mode = 0755 | S_IFDIR;
        st->st_nlink = 3;
        st->st_size = 4096;
		st->st_blksize  = FS_BLKSIZE;
		st->st_blocks   = ceil(st->st_size/st->st_blksize);
		st->st_uid      = 0;
		st->st_gid      = 0;
		
		return 0;
	}
	int ret = 0;
	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -EINVAL;
	}
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	}
	
	dicom::DirEntry de;
	ret = dicom::Lookup(gw_, pid, name, &de);
	if (ret != 0) {
		return -ENOENT;
	} 
	st->st_nlink 	= 1;
	st->st_size     = de.size;
	st->st_blksize  = FS_BLKSIZE;
	st->st_blocks   = ceil(st->st_size/st->st_blksize);
	if(de.is_folder == true) {
		st->st_mode     = 0755 | S_IFDIR;
	} else {
		st->st_mode     = 0666 | S_IFREG;
	}
	st->st_uid      = 0;
	st->st_gid      = 0;
	st->st_atime    = de.mtime;
	st->st_mtime    = de.mtime;
	st->st_ctime    = de.mtime;
	
	LOG("Getattr %s in %ld %ld.", name.c_str(), de.size, de.mtime );
	
	return 0;
}
int sur_impls::sur_rename(const char *from, const char *to)
{
	int ret = 0;
	string dir;
	string name;
	ret = deal_path(from, dir, name);
	if(ret != 0) {
		return -EINVAL;
	}
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	}

	dicom::DirEntry de;
	ret = dicom::Lookup(gw_, pid, name, &de);
	if (ret != 0) {
		return -ENOENT;
	} 
	
	ret = deal_path(to, dir, name);
	if(ret != 0) {
		return -EINVAL;
	}
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	}
	
	if(de.is_folder == true) {
		dicom::DirEntry tmp;
		ret = dicom::RenameDir(gw_, de.id, name, &tmp); 
	} else {
		ret = dicom::RenameFile(gw_, de.id, pid, name); 
	}
	
	if(ret != 0) {
		LOG("RENAME rename %s to %s fail in %d.", from, to, ret);
		return -EIO;
	}
	return 0;
}
int sur_impls::sur_readdir(const char *path, vector<DirEntry>& lctx)
{
	int ret = 0;
	string pid;
	ret = parse_pid(path, pid);
	if (ret != 0) {
		return -EIO;
	}

	//vector<DirEntry> lctx;
	ret = dicom::ReadDir(gw_, pid, &lctx);
	if (ret != 0) {
		LOG("READDIR readdir fail in %d.", ret);
		return -EIO;
	} 
	// for(list<string>::size_type i = 0; i < lctx.size(); ++i) {
		// ctx.push_back(lctx[i].name);
	// }
	
	return 0;
}
int sur_impls::sur_mkdir(const char *path)
{
	int ret = 0;
	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -EINVAL;
	}
	string pid;
	ret = parse_pid(dir, pid);
	if (ret != 0) {
		return -EIO;
	}
	dicom::DirEntry de;
	ret = CreateDir(gw_, pid, name, &de);
	if (ret != 0) {
		LOG("MKDIR create dir fail in %d.", ret);
		return -EIO;
	} 
	return 0;
}
int sur_impls::sur_rmdir(const char *path)
{
	int ret = 0;

	string pid;
	ret = parse_pid(path, pid);
	if (ret != 0) {
		return -EIO;
	}

	ret = dicom::DeleteDir(gw_, pid);
	if (ret != 0) {
		LOG("RMDIR delete dir fail in %d (%s).", ret, pid.c_str());
		return -EIO;
	} 
	return 0;
}
int sur_impls::sur_utimens(const char *path, const struct timespec ts[2])
{
	return 0;
}
int sur_impls::sur_chmod(const char *path, mode_t mode)
{
	return 0;
}

inline int sur_impls::deal_path(const string& path, string& dir, string& f_name)
{
    int i = path.find_last_of("/");
    dir = path.substr(0, i);
    f_name = path.substr(i+1);
    return 0;

}
int sur_impls::parse_pid(const string& path, string& pid)
{
	if(path == "" || path == "/") {
		pid = "0";
		return 0;
	}

	int ret = 0;
	string dir;
	string name;
	ret = deal_path(path, dir, name);
	if(ret != 0) {
		return -1;
	}
	string lpid;
	ret = parse_pid(dir, lpid);
	if(ret != 0) {
		return -1;
	}
	dicom::DirEntry de;
	ret = Lookup(gw_, lpid, name, &de);
	if (ret != 0) {
		LOG("Fail when lookup gw id in %d.", ret);
		return -1;
	}
	pid = de.id;
	
	return 0;
}
